package x86;

import temp.Label;
import temp.Temp;
import tree.Exp;
import tree.Stm;
import util.List;
import assem.Instr;

public class Codegen
{
	Frame frame;
	public Codegen(Frame f)
	{
		frame=f;        
	}

	private List<Instr> ilist=null;
	private List<Instr> last=null;

	private void emit(Instr inst)
	{
		if (last!=null)
			last = last.tail = new List<Instr>(inst,null);
		else 
			last = ilist = new List<Instr>(inst,null);
	}

	private void munchStm (tree.Stm s) {
		/* Call method for statement subtype */
		if (s instanceof tree.CJUMP)
			munchCjump((tree.CJUMP)s);
		else if (s instanceof tree.EXPSTM)
			munchExpstm((tree.EXPSTM)s);
		else if (s instanceof tree.JUMP)
			munchJump((tree.JUMP)s);
		else if (s instanceof tree.LABEL)
			munchLabel((tree.LABEL)s);
		else if (s instanceof tree.MOVE)
			munchMove((tree.MOVE)s);
	}

	private Temp munchExp (tree.Exp e) {
		/* Call method for expression subtype */
		if (e instanceof tree.BINOP)
			return munchBinop((tree.BINOP)e);
		else if (e instanceof tree.CALL)
			return munchCall((tree.CALL)e);
		else if (e instanceof tree.CONST)
			return munchConst((tree.CONST)e);
		else if (e instanceof tree.MEM)
			return munchMem((tree.MEM)e);
		else if (e instanceof tree.NAME)
			return munchName((tree.NAME)e);
		else if (e instanceof tree.TEMP)
			return munchTemp((tree.TEMP)e);
		else
			return null;
	}

	/*-------------------------------------------------------------*
	 *                      MUNCH STATEMENTS                       *
	 *-------------------------------------------------------------*/

	/* EXPSTM statement
	 * Only munch the correspondent expression
	 */
	private void munchExpstm(tree.EXPSTM s)
	{
		munchExp(s.exp);
	}

	/* CJUMP statement
	 * Implement conditional jumps
	 */
	private void munchCjump(tree.CJUMP s)
	{
		// auxiliar label for jump out of range */
		Label lAux = new Label();

		// conditional jump
		String jumpConditional = null;

		switch(s.op)
		{
		case tree.CJUMP.EQ:
			jumpConditional = "je ";
			break;
		case tree.CJUMP.NE:
			jumpConditional = "jne ";
			break;
		case tree.CJUMP.LT:
			jumpConditional = "jl ";
			break;
		case tree.CJUMP.LE:
			jumpConditional = "jle ";
			break;
		case tree.CJUMP.GT:
			jumpConditional = "jg ";
			break;
		case tree.CJUMP.GE:
			jumpConditional = "jge ";
			break;
		case tree.CJUMP.ULT:
			jumpConditional = "jb ";
			break;
		case tree.CJUMP.ULE:
			jumpConditional = "jbe ";
			break;
		case tree.CJUMP.UGT:
			jumpConditional = "ja ";
			break;
		case tree.CJUMP.UGE:
			jumpConditional = "jae ";
			break;
		}

		Temp left = munchExp(s.left);
		Temp right = munchExp(s.right);

		// CMP
		emit(new assem.OPER("cmp `s0, `s1", null, new List<Temp>(left, new List<Temp> (right, null))));
		// JUMP
		emit(new assem.OPER(jumpConditional + "`j0", null, null, new List<Label> (lAux, new List<Label> (s.ifTrue, new List<Label> (s.ifFalse, null)))));

		// LABELS
		emit(new assem.OPER("jmp `j0", null, null, new List<Label>(s.ifFalse, null)));
		munchLabel(new tree.LABEL(lAux));
		emit(new assem.OPER("jmp `j0", null, null, new List<Label>(s.ifTrue, null)));
	}

	/* JUMP statement
	 * Implement unconditional jumps
	 * Two types, jump to register value and jump to immediate address
	 */
	private void munchJump(tree.JUMP s)
	{
		/* Jump to immediate address */
		if (s.exp instanceof tree.NAME)
		{
			/* Get label from NAME node */
			tree.NAME l = (tree.NAME)s.exp;
			emit(new assem.OPER("jmp `j0", null, null, new List<Label>(l.label, null)));
		}
		/* Jump to address in register */
		else
		{
			Temp jmpAddr = munchExp(s.exp);
			emit(new assem.OPER("jmp `s0", null, new List<Temp>(jmpAddr,null), s.targets));
		}
	}

	/* LABEL statement
	 * Implement label statement
	 * Basic: "label_name:"    
	 */
	private void munchLabel(tree.LABEL s)
	{
		emit(new assem.LABEL(s.label + ":", s.label));
	}

	/* MOVE statement
	 * 
	 * mov reg, reg
	 * mov mem, reg
	 * mov reg, const
	 */
	private void munchMove(tree.MOVE s)
	{
		if (s.dst instanceof tree.MEM)
		{
			/* Use OPER because it is not a move between registers */
			Temp regTemp = munchExp(s.src);
			Temp memTemp = munchExp(((tree.MEM)s.dst).exp);
			emit(new assem.OPER("mov [`s0], `s1", null, new List<Temp>(memTemp, new List<Temp>(regTemp, null))));
		}
		else if(s.src instanceof tree.CONST)
		{
			emit(new assem.OPER("mov `d0, " + ((tree.CONST)s.src).value, new List<Temp>(munchExp(s.dst), null), null));
		}
		else
		{
			/* Use assem.MOVE because it needs this info on the RegAlloc phase */
			emit(new assem.MOVE("mov `d0, `s0", munchExp(s.dst), munchExp(s.src)));
		}
	}

	/*-------------------------------------------------------------*
	 *                      MUNCH EXPRESSIONS                      *
	 *-------------------------------------------------------------*/

	/* BINOP expression
	 * Operations are:
	 * 	Arithmetic: PLUS, MINUS, TIMES and DIV
	 *  Bitwise: AND, OR and XOR
	 *  Logical shift: LSHIFT, RSHIFT 
	 *  Aritmetic right shift: ARSHIFT
	 */
	@SuppressWarnings("static-access")
	private Temp munchBinop(tree.BINOP e)
	{
		Temp t = new Temp();
		Temp left = munchExp(e.left);
		Temp right;

		switch (e.binop)
		{
		/* ARITHMETIC */
		case tree.BINOP.PLUS:
			if(e.right instanceof tree.CONST)
			{
				emit(new assem.OPER("mov `d0, `s0", new List<Temp>(t, null), new List<Temp>(left, null)));
				emit(new assem.OPER("add `d0, " + ((tree.CONST)e.right).value, new List<Temp>(t, null), null));
			}
			else
			{
				right = munchExp(e.right);
				emit(new assem.OPER("mov `d0, `s0", new List<Temp>(t, null), new List<Temp>(left, null)));
				emit(new assem.OPER("add `d0, `s0", new List<Temp>(t, null), new List<Temp>(right, null)));
			}
			break;
		case tree.BINOP.MINUS:
			if(e.right instanceof tree.CONST)
			{
				emit(new assem.OPER("mov `d0, `s0", new List<Temp>(t, null), new List<Temp>(left, null)));
				emit(new assem.OPER("sub `d0, " + ((tree.CONST)e.right).value, new List<Temp>(t, null), null));
			}
			else
			{
				right = munchExp(e.right);
				emit(new assem.OPER("mov `d0, `s0", new List<Temp>(t, null), new List<Temp>(left, null)));
				emit(new assem.OPER("sub `d0, `s0", new List<Temp>(t, null), new List<Temp>(right, null)));
			}
			break;
		case tree.BINOP.TIMES:
			right = munchExp(e.right);
			emit(new assem.OPER("mov `d0, `s0", new List<Temp>(frame.eax, null), new List<Temp>(left, null)));
			// Uses EAX and defines EAX, EDX
			emit(new assem.OPER("imul `s0", new List<Temp>(frame.eax, new List<Temp>(frame.edx, null)), 
					new List<Temp>(right, new List<Temp>(frame.eax, null))));
			emit(new assem.OPER("mov `d0, `s0", new List<Temp>(t, null), new List<Temp>(frame.eax, null)));
			break;
		case tree.BINOP.DIV:
			right = munchExp(e.right);
			emit(new assem.OPER("mov `d0, 0", new List<Temp>(frame.edx, null), null));
			emit(new assem.OPER("mov `d0, `s0", new List<Temp>(frame.eax, null), new List<Temp>(left, null)));
			// Uses EAX, EDX and defines EAX
			emit(new assem.OPER("idiv `s0", new List<Temp>(frame.eax, null), 
					new List<Temp>(right, new List<Temp>(frame.eax, new List<Temp>(frame.edx, null)))));
			emit(new assem.OPER("mov `d0, `s0", new List<Temp>(t, null), new List<Temp>(frame.eax, null)));
			break;

			/* BITWISE */
		case tree.BINOP.AND:
			if(e.right instanceof tree.CONST)
			{
				emit(new assem.OPER("mov `d0, `s0", new List<Temp>(t, null), new List<Temp>(left, null)));
				emit(new assem.OPER("and `d0, " + ((tree.CONST)e.right).value, new List<Temp>(t, null), null));
			}
			else
			{
				right = munchExp(e.right);
				emit(new assem.OPER("mov `d0, `s0", new List<Temp>(t, null), new List<Temp>(left, null)));
				emit(new assem.OPER("and `d0, `s0", new List<Temp>(t, null), new List<Temp>(right, null)));
			}
			break;
		case tree.BINOP.OR:
			if(e.right instanceof tree.CONST)
			{
				emit(new assem.OPER("mov `d0, `s0", new List<Temp>(t, null), new List<Temp>(left, null)));
				emit(new assem.OPER("or `d0, " + ((tree.CONST)e.right).value, new List<Temp>(t, null), null));
			}
			else
			{
				right = munchExp(e.right);
				emit(new assem.OPER("mov `d0, `s0", new List<Temp>(t, null), new List<Temp>(left, null)));
				emit(new assem.OPER("or `d0, `s0", new List<Temp>(t, null), new List<Temp>(right, null)));
			}
			break;
		case tree.BINOP.XOR:
			if(e.right instanceof tree.CONST)
			{
				emit(new assem.OPER("mov `d0, `s0", new List<Temp>(t, null), new List<Temp>(left, null)));
				emit(new assem.OPER("xor `d0, " + ((tree.CONST)e.right).value, new List<Temp>(t, null), null));
			}
			else
			{
				right = munchExp(e.right);
				emit(new assem.OPER("mov `d0, `s0", new List<Temp>(t, null), new List<Temp>(left, null)));
				emit(new assem.OPER("xor `d0, `s0", new List<Temp>(t, null), new List<Temp>(right, null)));
			}
			break;

			/* SHIFT */
		case tree.BINOP.ARSHIFT:
			emit(new assem.OPER("mov `d0, `s0", new List<Temp>(t, null), new List<Temp>(left, null)));
			emit(new assem.OPER("sar `d0, " + String.valueOf(((tree.CONST)e.right).value), new List<Temp>(t, null), new List<Temp>(t, null)));
			break;
		case tree.BINOP.LSHIFT:
			emit(new assem.OPER("mov `d0, `s0", new List<Temp>(t, null), new List<Temp>(left, null)));
			emit(new assem.OPER("shl `d0, " + String.valueOf(((tree.CONST)e.right).value), new List<Temp>(t, null), new List<Temp>(t, null)));
			break;
		case tree.BINOP.RSHIFT:
			emit(new assem.OPER("mov `d0, `s0", new List<Temp>(t, null), new List<Temp>(left, null)));
			emit(new assem.OPER("shr `d0, " + String.valueOf(((tree.CONST)e.right).value), new List<Temp>(t, null), new List<Temp>(t, null)));
			break;
		}

		return t;
	}

	private Temp munchCall(tree.CALL e)
	{
		int size = 0;

		if(e.args != null)
			size = e.args.size();

		// Ajusta ponteiro da pilha
		emit(new assem.OPER("sub `s0, "+ size * frame.wordsize(), null, new List<Temp>(Frame.esp, null)));

		List<Exp> list = e.args;
		for( int i = 0; list!=null ;list = list.tail, i++)
		{
			Temp temp = munchExp(list.head);
			emit(new assem.OPER("mov [`s1+" + i * frame.wordsize()+ "], `s0", null, new List<Temp>(temp, new List<Temp>(Frame.esp, null))));
		}

		// Emissao de call
		if(e.func instanceof tree.NAME)
		{
			emit(new assem.OPER("call " + ((tree.NAME)e.func).label, Frame.calldefs, null));
		}
		else
		{
			Temp temp = munchExp(e.func);
			emit(new assem.OPER("call `s0", Frame.calldefs, new List<Temp>(temp, null)));
		}

		//Recuperando argumentos
		emit(new assem.OPER("add esp, " + size * frame.wordsize(), new List<Temp>(Frame.esp, null), new List<Temp>(Frame.esp, null)));
		Temp temp = new Temp();
		emit(new assem.MOVE("mov `d0, `s0", temp, frame.RV()));

		return temp;
	}

	/* CONST expression
	 * Move the constant to a register
	 */
	private Temp munchConst(tree.CONST e)
	{
		Temp t = new Temp();
		emit(new assem.OPER("mov `d0, " + e.value, new List<Temp>(t,null), null));
		return t;
	}

	/* MEM expression
	 * Move the address given by expression to a register
	 */
	private Temp munchMem(tree.MEM e)
	{
		Temp t = new Temp();
		Temp memAddr = munchExp(e.exp);
		emit(new assem.OPER("mov `d0, [`s0]", new List<Temp>(t, null), new List<Temp>(memAddr, null)));
		return t;
	}

	/* NAME expression
	 * Move the label to a register
	 */
	private Temp munchName(tree.NAME e)
	{
		Temp t = new Temp();
		emit(new assem.OPER("mov `d0, " + e.label, new List<Temp>(t,null), null));
		return t;
	}

	/* TEMP expression
	 * Return the temp in the expression
	 */
	private Temp munchTemp(tree.TEMP e)
	{
		return e.temp;
	}

	/*-------------------------------------------------------------*
	 *                              MAIN                           *
	 *-------------------------------------------------------------*/
	List<Instr> codegen(Stm s)
	{
		List<Instr> l;
		munchStm(s);
		l=ilist;
		ilist=last=null;
		return l;
	}

	List<Instr> codegen(List<Stm> body)
	{
		List<Instr> l = null, t = null;

		for( ; body != null; body = body.tail )
		{
			munchStm(body.head);
			if ( l == null )
				l = ilist;
			else
				t.tail = ilist;
			t = last;
			ilist=last=null;
		}
		return l;
	}
}
